﻿using Newtonsoft.Json;
using OneDark.Evthing;
using OneDark.Sqlite;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using OneDark.Git;

namespace OneDark.Update
{
    internal class GitUpdate
    {
        private const string strGitRepos = "GitProperties.json";
        private List<GitProperty> gitProperties = new List<GitProperty>();
        private GITDB oGitDb = new GITDB();

        /// <summary>
        /// 异步获取Git仓库路径列表
        /// </summary>
        /// <param name="strFilter">过滤条件</param>
        /// <returns></returns>
        public async Task<List<GitProperty>> GetGitPathsAsync(string strFilter)
        {
            List<GitProperty> task = null;
            await Task.Run(() => task = GetGitPaths(strFilter));

            return task;
        }

        /// <summary>
        /// 获取Git仓库路径列表
        /// </summary>
        /// <param name="strFilter">过滤条件</param>
        /// <returns></returns>
        public List<GitProperty> GetGitPaths(string strFilter)
        {
            UInt32 i;
            gitProperties.Clear();
            // set the search
            Everything.Everything_SetSearchW(strFilter);

            // request name and size
            Everything.Everything_SetRequestFlags((uint)(Request.FILE_NAME | Request.PATH | Request.DATE_MODIFIED | Request.SIZE));

            Everything.Everything_SetSort((uint)Sort.DATE_MODIFIED_ASCENDING);

            // execute the query
            Everything.Everything_QueryW(true);

            // sort by path
            Everything.Everything_SortResultsByPath();

            // loop through the results, adding each result to the listbox.
            for (i = 0; i < Everything.Everything_GetNumResults(); i++)
            {
                Everything.Everything_GetResultDateModified(i, out long date_modified);

                // add it to the list box
                string strPath = Marshal.PtrToStringUni(Everything.Everything_GetResultPath(i));
                string strName = Marshal.PtrToStringUni(Everything.Everything_GetResultFileName(i));
                GitProperty gitProperty = new GitProperty()
                {
                    UpTime = Directory.GetLastWriteTime(strPath).Date,
                    Path = strPath,
                    Folder = strPath.Substring(0, strPath.LastIndexOf("\\")),
                    Status = RepoStatus.Normal
                };

                if (!string.IsNullOrEmpty(GetGitParam(strPath + "\\" + strName, ref gitProperty)))
                {
                    gitProperties.Add(gitProperty);
                }
            }
            return gitProperties;
        }

        /// <summary>
        /// 获取单个Git仓库信息
        /// </summary>
        /// <param name="strPath">Git仓库路径</param>
        /// <param name="gitProperty">Git仓库信息</param>
        /// <returns>Git仓库路径</returns>
        private string GetGitParam(string strPath, ref GitProperty gitProperty)
        {
            string strConfig = strPath + "\\config";
            string strHead = strPath + "\\HEAD";
            if (!File.Exists(strConfig) || !File.Exists(strHead))
            {
                return string.Empty;
            }

            IniFile iniFile = new IniFile(strConfig);
            List<string> lstSec = iniFile.GetSectionNames().ToList();

            if (lstSec.Contains("remote \"origin\""))
            {
                Dictionary<string, string> dicVal = iniFile.GetSectionValues("remote \"origin\"");

                dicVal.TryGetValue("url", out string strval);
                if (!string.IsNullOrEmpty(strval))
                {
                    if (strval.StartsWith("git@github.com:"))
                    {
                        strval = strval.Replace("git@github.com:", "https://github.com/");
                        iniFile.WriteValue("remote \"origin\"", "url", strval);
                    }

                    if (strval.ToLower().Contains("gitee"))
                    {
                        gitProperty.GitSource = RepoSource.Gitee;
                    }
                    else if (strval.ToLower().Contains("github"))
                    {
                        gitProperty.GitSource = RepoSource.GitHub;
                    }
                    else if (strval.ToLower().Contains("oschina"))
                    {
                        gitProperty.GitSource = RepoSource.Oschina;
                    }
                }

                gitProperty.Url = strval;
                dicVal.TryGetValue("puttykeyfile", out strval);
                gitProperty.PuttyKeyFile = strval;

                gitProperty.Project = gitProperty.Url.Substring(gitProperty.Url.LastIndexOf('/') + 1);
                if (gitProperty.Project.EndsWith(".git"))
                {
                    gitProperty.Project = gitProperty.Project.Substring(0, gitProperty.Project.LastIndexOf('.'));
                }
            }
            if (string.IsNullOrEmpty(gitProperty.Url))
            {
                gitProperty.Status = RepoStatus.Unversioned;
            }
            using (StreamReader sr = new StreamReader(strHead))
            {
                string line;
                // 从文件读取并显示行，直到文件的末尾
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.Contains("/"))
                    {
                        gitProperty.Branch = line.Substring(line.LastIndexOf('/') + 1);
                    }
                }
            }
            return strPath;
        }

        private int iProcedItems = 0;

        /// <summary>
        /// 更新扩展信息
        /// </summary>
        /// <param name="item"></param>
        /// <param name="progress"></param>
        private void UpdateExtParas(ref GitProperty item, IProgress<float> progress)
        {
            do
            {
                Repository oRep = new Repository()
                {
                    Name = item.Project,
                    Path = item.Path
                };
                oRep.Open();
                List<Commit> commits = oRep.Commits("-n 1");
                if (commits.Count <= 0)
                {
                    break;
                }
                if (string.IsNullOrEmpty(commits[0].Committer.Time))
                {
                    break;
                }
                try
                {
                    item.LastCommit = commits.Count > 0 ? DateTime.Parse(commits[0].Committer.Time).Date : DateTime.MinValue;
                    item.Commits = oRep.TotalCommits();
                    item.LocalChanges = oRep.LocalChanges().Count;
                    if (item.LocalChanges > 0 && !string.IsNullOrEmpty(item.Url))
                    {
                        item.Status = RepoStatus.Conflict;
                    }
                    oGitDb.Update(item);
                }
                catch { }

                oRep.Close();
            } while (false);
            iProcedItems++;
            if (progress != null)
                progress.Report((float)++iProcedItems / gitProperties.Count);
        }

        public async Task BatchUpdateExtParas(IProgress<float> progress)
        {
            iProcedItems = 0;
            await Task.Run(() => gitProperties.AsParallel().ForAll(p => UpdateExtParas(ref p, progress)));
        }

        private CancellationTokenSource cts;

        /// <summary>
        /// 批量更新
        /// </summary>
        /// <param name="iThreads"></param>
        /// <param name="process"></param>
        public void RunUpdate(int iThreads, IProgress<float> process)
        {
            int iUpdated = 0;
            // 创建调度器
            LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(iThreads);
            List<Task> tasks = new List<Task>();
            cts = new CancellationTokenSource();
            // Create a TaskFactory and pass it our custom scheduler.
            TaskFactory factory = new TaskFactory(lcts);

            gitProperties.ForEach(item =>
            {
                item.OnPullMsg += (msg) =>
                {
                    var repss = gitProperties.Where(p => !string.IsNullOrEmpty(p.PullMsg));
                    if (msg.Contains("Already up to date"))
                    {
                        item.LastPullTime = DateTime.Now;
                        oGitDb.Update(item);
                    }
                    if (msg.Contains("No such file or directory"))
                    {
                        item.LastPullTime = DateTime.Now;
                        item.Status = RepoStatus.RemotedDeleted;
                        //Save();
                        oGitDb.Update(item);
                    }
                    if (msg.Contains("Please commit your changes or stash them before you merge"))
                    {
                        item.Status = RepoStatus.Conflict_Update;
                        oGitDb.Update(item);

                        //Save();
                    }
                    item.PullMsg = msg;
                };


                // 超过100天没有提交过的代码10天更新一次
                //if (DateTime.Now - item.LastPullTime < TimeSpan.FromDays(10) &&
                //DateTime.Now - item.LastCommit > TimeSpan.FromDays(100))
                //{
                //    item.PullMsg = "Last Pull Time:" + item.LastPullTime.ToString();
                //    repository.Close();
                //    ++iUpdated;
                //    break;
                //}


                Task t = factory.StartNew(() =>
                {
                    //try
                    //{
                        Repository repository = new Repository()
                        {
                            Name = item.Project,
                            Path = item.Path
                        };
                        do
                        {
                            repository.Open();
                            string strBranch = repository.CurrentBranch()?.Name;
                            var remote = repository.Remotes().FirstOrDefault();
                            string strRemote = repository.Remotes().FirstOrDefault()?.Name;
                            if (string.IsNullOrEmpty(strBranch) || string.IsNullOrEmpty(remote?.Name))
                            {
                                break;
                            }
                            if (ConfigRdr.UseCnpmjs &&
                            item.GitSource == RepoSource.GitHub)
                            {
                                strRemote = "cnpmjs";
                                if (!repository.Remotes().Any(p => p.Name.Contains(strRemote)))
                                {
                                    string strRemoteUrl = remote.URL.Replace("https://github.com", ConfigRdr.CnpmjsGithubUrl);
                                    Remote.Add(repository, strRemote, strRemoteUrl);
                                }
                            }
                            item.PullStatus = RepoPullStatus.PullStart;
                            repository.Pull(strRemote, strBranch, item.OnPullMsg);
                            UpdateExtParas(ref item, null);
                        } while (false);
                        item.PullStatus = RepoPullStatus.PullFinished;
                        repository.Close();
                        process?.Report((float)++iUpdated / gitProperties.Count);
                    //}
                    //catch { }
                }, cts.Token);
                tasks.Add(t);
            });
            // Wait for the tasks to complete before displaying a completion message.
            Task.WaitAll(tasks.ToArray());
            cts.Dispose();
            Console.WriteLine("Successful completion.");
        }

        public void CancleUpdate()
        {
            cts.Cancel();
        }

        public async Task LoadReposAsync()
        {
            List<GitProperty> gitPosfile = null;
            await Task.Run(() => gitPosfile = Read(strGitRepos));
            var gitPos = oGitDb.Query<GitProperty>("select * from GitProperty");

            //DataTable dt = DatatableUtil<GitProperty>.ToDataTable<GitProperty>(gitPos.ToList());
            //DataTable dtfile = DatatableUtil<GitProperty>.ToDataTable<GitProperty>(gitPosfile.ToList());
            //string path = Environment.CurrentDirectory + "\\Source_" + DateTime.Now.ToString("yyyMMddHHmm") + ".xls";
            //NPOIHelper.Export(dt, "sheet", "db.xls");
            //NPOIHelper.Export(dtfile, "sheet", "file.xls");

            if (gitPos != null)
            {
                foreach (var gitRep in gitProperties)
                {
                    var reposFromFile = gitPos.Where(p => p.Path == gitRep.Path && p.Url == gitRep.Url).FirstOrDefault();
                    if (reposFromFile == null)
                    {
                        continue;
                    }
                    gitRep.LastCommit = reposFromFile.LastCommit;
                    gitRep.Commits = reposFromFile.Commits;
                    gitRep.LocalChanges = reposFromFile.LocalChanges;
                    gitRep.LastPullTime = reposFromFile.LastPullTime;
                    gitRep.Status = reposFromFile.Status;

                    //if (gitRep.LocalChanges > 0 && !string.IsNullOrEmpty(gitRep.Url))
                    //{
                    //    gitRep.Status = RepoStatus.Conflict;
                    //}
                }
                foreach (var gitRep in gitProperties)
                {
                    var reposFromFile = gitPos.Where(p => p.Folder == gitRep.Folder && p.Url == gitRep.Url).FirstOrDefault();
                    if (reposFromFile == null)
                    {
                        reposFromFile = gitRep;
                        UpdateExtParas(ref reposFromFile, null);
                        oGitDb.Insert(reposFromFile);
                    }
                    gitRep.LastCommit = reposFromFile.LastCommit;
                    gitRep.Commits = reposFromFile.Commits;
                    gitRep.LocalChanges = reposFromFile.LocalChanges;
                    gitRep.LastPullTime = reposFromFile.LastPullTime;
                    gitRep.Status = reposFromFile.Status;
                    //oGitDb.Update(reposFromFile);
                    //if (gitRep.LocalChanges > 0 && !string.IsNullOrEmpty(gitRep.Url))
                    //{
                    //    gitRep.Status = RepoStatus.Conflict;
                    //}
                }
            }
        }

        /// <summary>
        /// 检查本地仓库是否重复、重复则标签为灰色
        /// </summary>
        /// <param name="gitProperties"></param>
        public void CheckRespRepeat(List<GitProperty> gitProperties)
        {
            int iCnt = gitProperties.Count;
            for (int i = 0; i < iCnt; i++)
            {
                for (int j = i + 1; j < iCnt; j++)
                {
                    if (gitProperties[i].Url == gitProperties[j].Url &&
                        gitProperties[i].Branch == gitProperties[j].Branch)
                    {
                        gitProperties[i].Status = RepoStatus.Repeat;
                        oGitDb.Update(gitProperties[i]);
                        break;
                    }
                }
            }
        }

        public bool Save(string fileName, object obj = null)
        {
            try
            {
                if (obj == null)
                {
                    obj = gitProperties;
                }
                FileStream _file = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite);
                using (StreamWriter writer1 = new StreamWriter(_file))
                {
                    JsonSerializer serializer = new JsonSerializer { Formatting = Formatting.Indented };
                    serializer.Serialize(writer1, obj);
                    //writer.WriteLine(看你要写什么);
                    writer1.Flush();
                    writer1.Close();
                    _file.Close();
                }
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed reading {fileName}");
                return false;
            }
        }

        public List<GitProperty> Read(string fileName)
        {
            List<GitProperty> gitPops = null;
            Console.WriteLine($"Reading {fileName}");
            if (File.Exists(fileName))
            {
                try
                {
                    using (StreamReader r = new StreamReader(fileName))
                    {
                        string json = r.ReadToEnd();
                        gitPops = JsonConvert.DeserializeObject<List<GitProperty>>(json);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Failed reading {fileName}");
                }
            }
            return gitPops;
        }
    }
}